package com.rw.tool.util.expression.expression.calculator;

import com.rw.tool.util.expression.element.Calculation;
import com.rw.tool.util.expression.element.NumberElement;
import com.rw.tool.util.expression.exception.SignException;
import com.rw.tool.util.expression.expression.calculator.element.*;
import com.rw.tool.util.expression.expression.tree.ElementNode;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * 表达式计算器
 * <p>
 * 可以使用它做到扩展二元计算符号的目的
 * 理论上是能够支持一元运算符的，但需要修改的东西太多。
 * 你需要避开中缀表达式构造方法中的合法性检测，自己提供一套单独的合法性检测。
 * <p>
 * 关于一元运算符的扩展我并未测试过。在这里，仅测试和支持二元运算符。
 * 至于三元乃至更多的运算符，均无法使用此套工具实现
 */
public class Calculator {
    public BigDecimal calculation(ElementNode rootNode) {
        // 如果该节点是子节点，则直接返回数字
        if (rootNode.isChild()) {
            return ((NumberElement) rootNode.getElement()).getNumber();
        }

        // 如果不是子节点，则返回左节点和右节点的运算结果
        BigDecimal left = calculation(rootNode.getLeftNode());
        BigDecimal right = calculation(rootNode.getRightNode());
        return calculationOne(left, (Calculation) rootNode.getElement(), right);
    }

    /**
     * 计算一对数字
     */
    protected BigDecimal calculationOne(BigDecimal leftNumber, Calculation calculation, BigDecimal rightNumber) {
        List<CalculatorElement> calculatorElementList = getCalculatorElementList();

        for (CalculatorElement calculatorElement : calculatorElementList) {
            if (calculatorElement.trigger(calculation)) {
                return calculatorElement.handler(leftNumber, calculation, rightNumber);
            }
        }

        throw new SignException(calculation.toString());
    }

    /**
     * 计算一对数字
     */
    protected List<CalculatorElement> getCalculatorElementList() {
        List<CalculatorElement> calculatorElementList = new ArrayList<>();

        calculatorElementList.add(new PlusCalculatorElement());
        calculatorElementList.add(new MinusCalculatorElement());
        calculatorElementList.add(new DivisionCalculatorElement());
        calculatorElementList.add(new MultiCalculatorElement());

        return calculatorElementList;
    }
}
